/**
 * 
 */
package com.flashdeck.dao.impl;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;

import com.flashdeck.dao.DeckDao;
import com.flashdeck.dao.SettingsDao;
import com.flashdeck.domain.Deck;
import com.flashdeck.domain.DeckInfo;
import com.flashdeck.domain.DeckProgress;
import com.flashdeck.utils.DeckHandler;

/**
 * @author Vincentas
 * 
 */
public class DeckDaoImpl implements DeckDao {
	
	private static final String DECK_FILE_EXTENCION = "deck";
	
	private static final String DECK_PROGRESS_FILE_EXTENCION = "progress";
	
	private static DeckDaoImpl instance;
	
	public synchronized static DeckDao getInstance() {
		if (instance == null) {
			instance = new DeckDaoImpl();
		}
		
		return instance;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.flashdeck.dao.DeckDao#listDeck()
	 */
	@Override
	public List<DeckInfo> listDeckInfos() {
		File[] deckFiles = getDeckFiles();

		Set<DeckInfo> result = new HashSet<DeckInfo>();
		for (File deckFile : deckFiles) {
			DeckInfo deckInfo = DeckHandler.parseDeckInfo(deckFile);
			if (deckInfo != null) {
				result.add(deckInfo);
			}
		}

		return new ArrayList<DeckInfo>(result);
	}

	@Override
	public File[] getDeckFiles() {
		return getSettingsDao().getDeckFolder().listFiles(new ExtensioFileNameFiler(DECK_FILE_EXTENCION));
	}

	/**
	 * @return the settingsDao
	 */
	public SettingsDao getSettingsDao() {
		return SettingsDaoImpl.getInstance();
	}

	@Override
	public void removeDeck(DeckInfo deckInfo) {
		// TODO Auto-generated method stub		
	}

	@Override
	public DeckInfo getDeckInfo(String deckId) {
		List<DeckInfo> list = listDeckInfos();
		
		for (DeckInfo deckInfo : list) {
			if (deckInfo.getId().equals(deckId)) {
				return deckInfo;
			}
		}
		
		return null;
	}

	private Map<String, File> deckIndex = new HashMap<String, File>();
	
	private Map<String, File> loadDeckIndex(File folder) {
		Map<String, File> result = new HashMap<String, File>();
		
		File[] deckFiles = folder.listFiles(new ExtensioFileNameFiler(DECK_FILE_EXTENCION));
		for (File file : deckFiles) {
			DeckInfo deckInfo = DeckHandler.parseDeckInfo(file);
			result.put(deckInfo.getId(), file);
		}
		
		return result;
	}
	
	@Override
	public Deck getDeck(String deckId) {
		Deck result = null;
		
		if (!deckIndex.containsKey(deckId)) {
			deckIndex = loadDeckIndex(getSettingsDao().getDeckFolder());
		}	
		
		File deckFile = deckIndex.get(deckId);
		if (deckFile != null) {
			result = DeckHandler.parseDeck(deckFile);	
		}		
		
		return result;
	}

	/**
	 * Delete any progress file and recreates fresh progress file with all cards
	 * in first group
	 * 
	 * @param deckId
	 *            Deck id for which to reset progress
	 * @return Returns DeckProgress of reset deck
	 */
	private DeckProgress resetDeckProgress(String deckId) {
		File file = new File(getSettingsDao().getDeckFolder(), deckId + "." + DECK_PROGRESS_FILE_EXTENCION);
		FileUtils.deleteQuietly(file);
		
		Deck deck = getDeck(deckId);
		if (deck == null) {
			return null;
		}
		
		DeckProgress progress = new DeckProgress(deck.getCards(), deck.getDeckInfo().getId(), deck.getGroups());
		saveDeckProgress(progress);
		
		return progress;
	}
	
	/* (non-Javadoc)
	 * @see com.flashdeck.dao.DeckDao#getDeckProgress(java.lang.String)
	 */
	@Override
	public DeckProgress getDeckProgress(String deckId) {	
		File deckFile = new File(getSettingsDao().getDeckFolder(), deckId + "." + DECK_PROGRESS_FILE_EXTENCION);

		if (deckFile.exists() && deckFile.isFile()) {
			ObjectInputStream ois = null;
			try {
				ois = new ObjectInputStream(FileUtils.openInputStream(deckFile));
				return (DeckProgress) ois.readObject();
			} catch (Exception e) {
				/*
				 * Ignore any exception and reset progress
				 */
				e.printStackTrace();
				// TODO report exception
			} finally {
				if (ois != null) {
					IOUtils.closeQuietly(ois);
				}
			}			
		} 
		
		return resetDeckProgress(deckId);	
	}

	/* (non-Javadoc)
	 * @see com.flashdeck.dao.DeckDao#saveDeckProgress(com.flashdeck.domain.DeckProgress)
	 */
	@Override
	public void saveDeckProgress(DeckProgress deckProgress) {		
		ObjectOutputStream oos = null;
		File deckFolder = getSettingsDao().getDeckFolder();
		File tempFile = new File(deckFolder, "temp.progress");
		
		try {
			oos = new ObjectOutputStream(FileUtils.openOutputStream(tempFile));
			oos.writeObject(deckProgress);
			oos.flush();
			
			File progressFile = new File(deckFolder, deckProgress.getDeckId() + ".progress");
			FileUtils.forceDelete(progressFile);
			FileUtils.moveFile(tempFile, progressFile);			
		} catch (IOException e) {
			/*
			 * Ignore any exception and reset progress
			 */
			e.printStackTrace();
			// TODO report exception
		} finally {
			if (oos != null) {
				IOUtils.closeQuietly(oos);
			}
			
			FileUtils.deleteQuietly(tempFile);
		}
	}
	
	class ExtensioFileNameFiler implements FilenameFilter {

		private final String extension;
		
		public ExtensioFileNameFiler(String extension) {
			this.extension = extension;
		}
		
		@Override
		public boolean accept(File dir, String filename) {
			return FilenameUtils.isExtension(filename, extension);
		}
		
	}
}
